package cn.tedu.charging.order.service.impl;

import cn.tedu.charging.common.pojo.JsonResult;
import cn.tedu.charging.common.pojo.param.GunStatusUpdateParam;
import cn.tedu.charging.common.pojo.vo.StationInfoVO;
import cn.tedu.charging.common.pojo.vo.UserInfoVO;
import cn.tedu.charging.common.utils.JsonUtils;
import cn.tedu.charging.order.common.ChargingConstants;
import cn.tedu.charging.order.common.GunStatusConstants;
import cn.tedu.charging.order.common.MqttConstants;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.mqtt.MqttProducer;
import cn.tedu.charging.order.pojo.dto.ChargingDto;
import cn.tedu.charging.order.pojo.param.OrderAddParam;
import cn.tedu.charging.order.quartz.DeviceCheckJob;
import cn.tedu.charging.order.rabbitmq.RabbitMQProducer;
import cn.tedu.charging.order.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Random;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    /**
     * 1. 生成 订单号 生成的逻辑
     * 2. 修改 充电枪 的状态 为 使用中 (微服务架构  订单服务调用 设备服务 服务之间通信)
     * 3. 获取 用户信息 （车辆id）  (微服务架构  订单服务 调用 用户服务 服务之间通信)
     * 4. 获取 场站信息  （场站信息 和 设备信息 充电站id, 运营商id ... ） (微服务架构  订单服务调用 设备服务 服务之间通信)
     * 5. 发送 开始充电指令  订单服务 给 设备发  （物联网 订单服务 调用 充电桩设备 服务与 设备之间通信）
     * 6. 创建设备检查任务 用来检查设备无响应
     * @param orderAddParam
     * @return
     */
    @Override
    public String createOrder(OrderAddParam orderAddParam) {
        log.debug("创建订单-orderService:入参:{}", orderAddParam);
        //1 生成订单号
        log.debug("创建订单-orderService:生成订单号");
        String orderNo = getOrderNo();
        log.debug("创建订单-orderService:生成订单号:{}", orderNo);

        //2 获取用户信息 车辆id
        Integer userId = orderAddParam.getUserId();
        log.debug("创建订单-orderService:获取用户信息:入参:{}", userId);
        UserInfoVO userInfo = getUserInfoByOpenFeign(userId);
        log.debug("创建订单-orderService:获取用户信息:入参:{},出参:{}", userId,userInfo);

        //3 修改 充电枪 的状态 为 使用中
        // 1 使用中 魔数 应该定义为常量或者枚举
        log.debug("创建订单-orderService:修改枪的状态为使用中:入参:枪id:{},状态:{}",
                orderAddParam.getGunId(),GunStatusConstants.GUN_STATUS_USING);
        Boolean success = updateGunStatus(orderAddParam.getGunId(),
                GunStatusConstants.GUN_STATUS_USING);
        log.debug("创建订单-orderService:修改枪的状态为使用中:入参:枪id:{},状态:{},出参:{}",
                orderAddParam.getGunId(),GunStatusConstants.GUN_STATUS_USING,success);

        //4 获取 场站信息
        log.debug("创建订单-orderService:获取场站信息:入参:{}", orderAddParam.getGunId());
        StationInfoVO stationInfo = getStationInfo(orderAddParam.getGunId());
        log.debug("创建订单-orderService:获取场站信息:入参:{},出参:{}", orderAddParam.getGunId(),stationInfo);

        //5 发送开始充电指令
        startCharging(orderNo,orderAddParam.getPileId(),orderAddParam.getGunId());
        log.debug("创建订单-orderService:入参:{},出参:{}", orderAddParam,orderNo);

        //6. 创建设备检查任务 用来检查设备无响应
        //不是每次 创建订单 接口被调用 都创建定时任务
        //什么时候创建呢? 设备开始充电的时候 才去创建定时任务
        //1>> 订单服务收到设备的返回结果 并且结果是 开始充电成功
        // 代码应该写到MqttConsumer messageArrived 都创建定时任务

    /*    log.debug("创建订单-orderService:创建设备检查任务:");
        try {
            DeviceCheckJob deviceCheckJob = new DeviceCheckJob(orderNo);
        } catch (SchedulerException e) {
            log.error("创建订单-orderService:创建设备检查任务,异常:{}",e);
        }*/


        //7 发送消息到RabbitMQ通过死信队列实现延迟消息处理超时订单 普通方案
        //7 发送消息到RabbitMQ通过死信队列实现延迟消息进行设备检查 优化方案
        //设备正常情况下1分钟 之内 返回响应，如果超过一分钟没响应 认为设备无响应
        log.debug("创建订单-orderService:发送消息到RabbitMQ通过死信队列实现延迟消息处理超时订单:入参:{}", orderNo);
        Boolean result = sendMessage(orderNo);
        log.debug("创建订单-orderService:发送消息到RabbitMQ通过死信队列实现延迟消息处理超时订单:入参:{},出参:{}", orderNo,result);

        return orderNo;
    }

    @Autowired
    private RabbitMQProducer rabbitMQProducer;


    /**
     * 发送消息到RabbitMQ通过死信队列实现延迟消息处理超时订单
     * @return
     */
    private Boolean sendMessage(String orderNo){
        rabbitMQProducer.send(orderNo);
        return true;
    }


    @Autowired
    private MqttProducer mqttProducer;

    /**
     * 订单服务 给 设备发送 开始充电指令
     */
    private void startCharging(String orderNo,Integer pileId,Integer gunId) {
        //1> Topic的设计
        // 枪维度   一个枪一个Topic topic_桩id_枪id
        // 桩维度   一个桩一个Topic topic_桩id
        // 用桩维度 的topic的数量会比 枪维度的topic数量理论上会 节省资源
        // 用桩维度 消息体应该包含枪信息  枪id
        String topic = MqttConstants.TOPIC_START_CHARGING_PREFIX + pileId;
        //2> 消息的设计
        // 业务目标  订单服务 发送开始充电指令 到设备
        // 开始充电指令是一个消息  消息对于设备来说 是 入参
        // 思考 接口的入参 该如何设计
        //业务的目标 业务是什么？业务如果缺少了什么参数，会导致业务无法开始，这些参数就是入参的核心参数
        //首先明确业务目标
        //订单服务-->开始充电-->
        //给具体的设备(设备的信息)-->
        //发送具体的指令(开始充电指令)-->
        // 给具体的用户的具体的订单(订单的唯一标识订单号)
        // 订单信息的唯一标识 订单号
        // 告诉桩 用哪个枪 目标 给哪个用户的哪个订单 开始充电
        //2.1>自定义协议
        // |订单号|桩id|枪id|start| //自定义格式 按照分割符号分割
        //String message = "|1000_333_112233|666|6661|start|"
        // String 类 的常用方法 要熟悉
        // String[] arrays = message.split("|")
        //2.1 > json格式
        ChargingDto chargingDto = new ChargingDto();
        chargingDto.setOrderNo(orderNo);
        chargingDto.setPileId(pileId);
        chargingDto.setGunId(gunId);
        chargingDto.setMsg(ChargingConstants.START_CHARGING);
        //java对象怎么转换为Json
        //序列化 Spring-Boot MVC 已经帮我们自动做好了 对象转json json转对象
        //Jackson json库
        //json库 Gson FastJson ...
        //https://cloud.tencent.com/developer/article/2242912
        String message = JsonUtils.toJson(chargingDto);
        log.debug("发送开始充电指令:topic:{},消息:{}", topic,message);
        Boolean success = mqttProducer.send(topic, message);
        log.debug("发送开始充电指令:topic:{},消息:{},发送结果:{}", topic,message,success);

    }

    @Autowired
    private DeviceClient deviceClient;

    /**
     * 获取场站信息
     */
    private StationInfoVO getStationInfo(Integer gunId) {
        JsonResult<StationInfoVO> result = deviceClient.getStationInfo(gunId);
        if (result != null) {
            return result.getData();
        }
        return null;
    }


    /**
     * 更新枪的状态
     * @param gunId
     * @param status
     * @return
     */
    private Boolean updateGunStatus(Integer gunId, Integer status) {
        GunStatusUpdateParam param = new GunStatusUpdateParam();
        param.setGunId(gunId);
        param.setStatus(status);
        JsonResult<Boolean> result = deviceClient.updateGunStatus(param);
        if (result != null) {
            return result.getData();
        }
        return false;
    }

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private UserClient userClient;


    /**
     * 通过用户id 获取用户信息
     * @param userId
     * @return
     */
    private UserInfoVO getUserInfoByOpenFeign(Integer userId) {
        JsonResult<UserInfoVO> userInfo = userClient.getUserInfo(userId);
        return userInfo.getData();
    }

    /**
     * 通过用户id 获取用户信息
     * @param userId
     * @return
     */
    private UserInfoVO getUserInfo(Integer userId) {
        String url = getRandomUrl();
        log.debug("随机获取的用户服务的URL:{}", url);
        ParameterizedTypeReference<JsonResult<UserInfoVO>> repType =
                new ParameterizedTypeReference<JsonResult<UserInfoVO>>() {
                };

        ResponseEntity<JsonResult<UserInfoVO>> entity = restTemplate.
                exchange(url, HttpMethod.GET, null, repType, userId);

        JsonResult<UserInfoVO> body = entity.getBody();
        UserInfoVO data = body.getData();
        return data;
    }

    /**
     *  随机获取用户服务的URL
     * 1. 把用户服务的 URL 放到数组
     * 2. 获取 随机数 通过JavaAPI Random
     * 3. 把随机数作为数组的下标,去数组 通过 下标 获取 用户服务URL
     * 4. 通过URL调用用户服务
     * @return
     */
    private String getRandomUrl(){
        String url = "http://localhost:8080/user/info/{userId}";
        String url1 = "http://localhost:8081/user/info/{userId}";
        String url2 = "http://localhost:8082/user/info/{userId}";

        String[] urls = {url,url1,url2};
        //String[] urls = new String[]{url,url1,url2};
        Random random = new Random();
        int index = random.nextInt(urls.length);
        return urls[index];
    }





    /**
     * 生成订单号
     * @return
     */
    private String getOrderNo(){
        return "666";
    }

}
